分治法,递归求解一个问题,在每一层递归中应用分解,解决和合并三个步骤。
分解
将问题划分为子问题,子问题的形式与原问题一样,只是规模更小。
解决
如果子问题的规模足够小,则停止递归,直接求解
合并
将子问题的解组合成原问题的解
有时除了与原问题形式完全一样的规模更小的子问题外,还需要求解与原问题不完全一样的子问题,这些可以在合并步骤完成,
最大子数组
二叉树遍历
前序(根左右)
#### 非递归
栈保存的是访问树节点的顺序,先进后出。入栈表示先不访问节点,出栈表示访问该节点。将当前指针指向的节点又看做根节点。
先序遍历二叉树的时候,首先访问根结点,再访问左孩子,最后访问右孩子。
在二叉树先序遍历非递归算法中:
- 先将根结点压栈,在栈不为空的时候执行循环;
- 让栈顶元素p出栈,访问栈顶元素p;
- 如果p的右孩子不为空,则让其右孩子先进栈
- 如果p的左孩子不为空,则再让其左孩子进栈
(注意:进栈顺序一定是先右孩子,再左孩子)
1 | void preOrder(TreeNode *t){ |
中序(左根右)
非递归
栈保存的是访问树节点的顺序,先进后出。入栈表示先不访问节点,出栈表示访问该节点。将当前指针指向的节点又看做根节点。
中序遍历的顺序是从最左节点开始的,所以需要将根节点的左节点全部入栈,直到最左节点(即左子节点为空)也入栈,将最左节点出栈进行访问,然后将当前的指针P指向栈顶结点的右孩子,使其作为根节点继续相同的处理。
- 刚开始指针指向树的根节点。将节点入栈后将指针指向左子节点,表示的是优先访问左子节点,先不访问根节点。
- 继续入栈根节点,直到左子节点为空,就表示没有左子树了。
- 将栈顶元素出栈,表示访问该节点,即左子节点为空的根节点。
- 按照中序遍历规则,输出该节点。
- 将指针指向该节点的右子节点,表示访问右子树。继续遍历。
1 | void InOrder_NoRecur(TreeNode *t){ |
后序(左右根)
非递归
后序遍历的非递归实现是三种遍历方式中最难的一种。
后序遍历也是最左节点开始遍历,所以需要从根结点开始,将所有最左结点全部压栈,每当一个结点出栈时,都先扫描该结点的右子树,只有当一个结点的左孩子和右孩子结点均被访问过了,才能访问结点自身。
辅助变量:
- flag=1表示当前结点的左孩子为空或者已被访问
- TreeNode *pre:表示之前访问的节点
1 | void postOrder(TreeNode *t){ |
该算法具有一个特性:就是当访问某个结点时,栈中所保存的元素正好是这个结点的所有祖先。
那么知道了这个特性,我们就很容易解决下面如下问题:
(1).当给定一个叶子结点,要求输出该叶子结点的所有祖先
(2).输出根结点到所有叶子结点的路径
(3).如果二叉树结点的值是数值,那么求每条路径上值之和,也可以利用二叉树后序遍历的非递归算法这个特性
二叉树深度
问题:给定一个二叉树,求它的深度。
分解:树的深度可以分解为(左子树深度+1)(右子树深度+1)取其中的最大值。
解决:递归条件是空树返回0。
合并:可以采用参数返回,也可以采用void。
1 | int DepthOfTree(TreeNode *t){ |
判断平衡树(AVL)
平衡二叉树(AVL):
它或者是一颗空树;
或者具有以下性质的二叉树:
- 每个节点的左子树和右子树的深度之差(平衡因子)的绝对值不超过1。
- 它的左子树和右子树都是一颗平衡二叉树。
分解:根节点判断,左子节点判断,右子节点判断。这三种情况是问题一样但规模不同
解决:空树返回真,利用求二叉树深度函数进行判断
合并:根据AVL树的定义,只有在左右子树深度不超过1 && 左子树符合要求 && 右子树也符合要求,才能返回真。
最直接的求法:遍历二叉树,求每一个节点的左右子树深度做判断。时间复杂度是$O(N^2)$,太费时间。
(1)如果二叉树为空,返回真
(2)如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假
1 | bool IsAVL(TreeNode *t){ |
$O(N)$求法:遍历二叉树是不可避免的,所以应该在求深度的地方优化。采用后序遍历的方式(左右根),在左右子节点的时候顺便求深度即可优化算法,也就是自底向上遍历。
关键在于如何在遍历左右子节点时求深度:递归的时候需要保存左右子树的深度信息,需要额外的两个变量。在合并的时候只需要一个变量来保存深度就可以了。
1 | bool IsAVL(TreeNode *t,int &deep){ |
BST转双向链表
BST(Binary Sort Tree),二叉排序树,又名二叉查找树,又名二叉搜索树
它或者是一棵空树;
或者是具有下列性质的二叉树:
- 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 左、右子树也分别为二叉排序树;
题目:输入一棵二叉搜索树,将其转换为一个排序的双向链表。要求:不能创建任何新的结点,只能调整树中结点指针的指向。
分析:
- 输入:二叉树根节点指针TreeNode *root
- 输出:双向链表,应该指明头节点,即返回一个指针指向双向链表的第一个元素,需要一个指针变量head指向头节点,该指针不能随着函数变化,所以应该设置全局指针。
- 递归截止:返回nullptr
- 将左右子节点链接起来需要知道前一个节点信息,所以需要一个变量pre保存前一个节点。要保证每次调用pre的指向不变。可以使用指针的指针或者全局变量
- pre指向前一个节点,cur指向当前处理的根节点,进行连接即可。
- 右子树进行同样的操作。也就是递归处理右子树(当前根节点变成了右子节点)
1 | TreeNode *head=nullptr; |